超全对照!前端监控的性能指标与数据采集
导语 | 前端监控可以让你更了解自己的网站,更早地发现和解决存在的问题,再通过优化来提升网站的性能和体验。那么,如何衡量一个网站的好坏?有什么指标?性能数据如何采集?本文围绕这些问题和你一起探讨。
一、为什么要做前端性能监控
可能你也有过这样的经历:
有用户反馈你的网站很慢,然后你立马紧张地在浏览器上打开用户反馈的网站。经过检查,可能你的网站一切正常,也可能你的网站真的很慢,甚至打不开了。
有一天老板问你:“咱们的网站性能体验怎么样?”你该如何回答?“挺好的,很快,这个月没有发生过故障....”老板再问:“怎么个好法?” “很快打开”“有多快?” “没统计过....” 然后就没有然后了.....
如果我们有前端监控,就有能力:
第一时间发现问题
也许在用户反馈这个问题之前,该问题已经存在多日了,只是一直用户没有反馈。如果有性能方面的监控,可以第一时间去发现问题,及时解决,把影响面、影响时长降到最低。
全面掌握数据,驱动优化性能,提高系统稳定性
通过监控采集到页面性能、用户使用方面的数据,可以系统、全面地掌握系统运行情况。
提升用户体验
加快内容显示速度,缩短交互延时。
二、前端性能监控分类
性能监控,可分为两类:合成监控和真实用户监控。
1. 合成监控
模拟一个用户使用场景,提交需要进行分析的页面,再通过一系列的打点、分析去完成一些指标项的数据收集,最后呈现分析报告。例如Google的 Lighthouse,目前最新版的google chrome自带的页面性能分析工具。
调出开发工具(win: F12, mac: fn+f12)
Lighthouse主要三个指标:性能、可交互性、最佳实践。
在性能方面,具体的指标有:
First Contentful Paint 首次内容渲染;
Time to Interactive 可交互时间;
Speed Index 速度指数;
Total Blocking Time 总的阻塞时间;
Largest Contentful Paint 最大内容渲染;
Cumulative Layout Shift 累积布局偏移;
每一项指标还会给出具体的优化建议,例如性能方面的优化建议:
Lighthouse 系统架构图:
2. 真实用户监控
真实用户监控, 记录的是真实的用户当时访问页面时的真实的数据,在访问结果时把采集到的数据上报到服务器,再经过数据清洗、加工等工作后,在监控平台上呈现监控数据。
3. 合成监控和真实用户监控的区别
合成监控的优缺点:
优点 | 缺点 |
---|---|
使用简单,现有工具 | 模拟用户场景,无法全部还原真实场景 |
采集数据丰富,如硬件指标、瀑布图 | 单次运行,数据不够稳定 |
不影响真实用户访问性能 | 数据量少,无法覆盖所有场景 |
真实用户监控的优缺点:
优点 | 缺点 |
---|---|
采集用户真实使用数据 | 无法采集到硬件相关信息 |
样本量大,可以全覆盖,减少统计误差 | 因需要上报,无法采集完整的资源加载瀑布图 |
性能数据与其它数据关联产生更大的价值 | 无法可视化展示页面加载过程 |
区别:
对比项 | 合成监控 | 真实用户监控 |
---|---|---|
实现难度&成本 | 较低 | 较高 |
采集数据丰富度 | 丰富 | 基础 |
采集样本量 | 小 | 大 |
适用场景 | 自有业务,用户量小,定性分析 | 中台产品,用户量大,定量分析 |
因为真实用户监控也是在运行时执行,所以这种真实用户监控比较难采集到一些硬件相关的指标,也很难去采集这个页面执行的幻灯片(即逐帧截图)。当然技术上可以用JS把当前页面保存成一个Canvas,做一些逐帧对比,甚至把这些数据回传回去。但是在实践过程中,我们肯定不会这样做,因为这对用户的流量是极大的浪费。介绍完这两种监控方案我们来看它们的对比。
本文要讲的是真实用户监控。
三、前端性能如何衡量
1. Google Web Vitals
评估一个网站的用户体验涉及到多个指标,有些还与网站的内容有关,但还是有一些共性的指标的,而Core Web Vitals体现了最关键的几项指标。此类核心用户体验需求包括页面内容的加载体验、交互性和视觉稳定性,这些方面共同组成 2020 Core Web Vitals 的基础。
LCP:Largest Contentful Paint,显示最大内容元素所需时间 (衡量网站初次载入速度);
FID:First Input Delay 首次输入延迟时间 (衡量网站互动顺畅程度);
CLS:Cumulative Layout Shift 累计布局位移 (衡量网页元件视觉稳定性);
除了以上三个主要衡量指标,还有FCP和TTFB:
FCP: First Contentful Paint 首次内容绘制,标记浏览器渲染来自 DOM 第一位内容的时间点;
TTFB: Time To First Byte 读取页面第一个字节的时间;
虽然LCP最大内容绘制是最重要的负载指标,但其也高度依赖于首次内容绘制 (FCP) 和首字节响应时间 (TTFB),这些指标对监控和改进均具有非常重要的意义。
2. API耗时
很多时候页面上的数据是通过异步请求后台API后再进行渲染得到的,API耗时直接影响了LCP数据和用户体验。
LCP 最大内容渲染
LCP以用户为中心,来衡量页面加载“完成”所用的时间,当页面中最大一块内容被渲染出来时,被认为是页面加载“完成”了。以前,用load\DOMContentLoaded件反应页面加载速度,后来使用了更精准的FCP(首次内容渲染),但是从用户角度出发,只有主要的内容显示出来了才算是加载完成。
其最大指的是实际Element长宽大小,Margin / Padding / Border等CSS大小效果不计入。包含的种类为<img>,url,<video>及包含文字节点的Block或Inline Element,未来可能会再加入<svg>。因为网页上的Element可能持续加载,最大的Element也可能持续改变 (如文字载入完,然后载入图片) ,所以当每一个当下最大的Element载完,浏览器会发出一个PerformanceEntry Metric,直到使用者可以进行Keydown / Scrolling / Tapping等操作,Browser才会停止发送Entry,故只要抓到最后一次Entry,即能判断LCP的持续时间。
如下图所示,绿色区域是LCP不断改变的侦测对象,也能看到FCP与LCP的判断差异。
FID 衡量网站互动顺畅程度
如何衡量网站操作的顺畅程度,Google采用FID指标,其定义为在TTI的时间内第一个互动事件的开始时间与浏览器回应事件的时间差,其互动事件为单次事件如Clicks / Taps / Key Presses等,其他连续性事件Scrolling / Zooming则不计,如下图所示:
为什么要取在TTI发生的第一次的操作事件,Google给的理由有以下三点:
3)但是FID的计算有其明显的问题,如当使用者在Main Thread闲置时操作,那FID可能就短,若不操作则FID则无法计算。这对开发者来说,很难去衡量网站的FID符合良好的标准,所以Google给的建议是透过降低TBT的时间来降低FID的值,当TBT越短,其FID就越好。
CLS 衡量网页元件视觉稳定性
你可能有过这样的经历, 当你准备点某一个按钮或内容是,它突然移动了,然后你点了另外一个按钮。
例如下图,当你准备点击“确认提交”按钮时,按钮上方加载了一个提示框,导致下面的按钮被往下移动,在你原来要点击的位置的元素由原来的“确认提交”按钮,变成了“放弃申请”按钮。在你点击时,变成了放弃该订单,前面的工作白白被浪费了,这是大家都不愿意看到的,体验非常不好,令人抓狂。
导致这种预料之外的的内容布局移动,可能是因为资源的异步加载、JS对dom元素的动态操作、未知尺寸图片加载等等。对用户来说,这是一种糟糕的用户体验。CLS就是用来衡量此类的体能指标。
什么是一个好的CLS分数?75%以上的用户小于0.1。
布局偏移的具体内容
布局偏移是由Layout Instability API定义的。这个API会在任意时间上报layout-shift的条目,当一个可见元素在两帧之间,改变了它的起始位置(默认的writing mode下指的是top和left属性)。这些元素被当成不稳定元素。
需要注意的是,布局偏移只发生在已经存在的元素改变起始位置的时候。如果一个新的元素被添加到dom上,或者已存在的元素改变它的尺寸,除非改变了其他元素的起始位置,否则都不算布局偏移。
其 CLS 代表的是每个元素非预期位移的累积,而每个位移的算法如下: 元素位移分数 (Layout Shift Score) = 影响范围 (Impact Fraction) * 移动距离 (Distance Fraction)。
上图中,元素在一帧中占了屏幕的一半。下一帧,元素下移了25%的视图高度。红色虚线框起来的部分就是不稳定元素在两帧的占的视图总和(75%),所以影响分数是0.75。
上图,不稳定元素在纵向移动了25%,所以距离分数是0.25。
所以布局偏移分数是:
CLS: 0.75 * 0.25 = 0.1875
API 指标关注哪些数据?
除了从请求到返回的耗时外,还有请求列队时间、请求发起时间。
如果一个API从发起请求到数据返回很快,但是由于需要列队等待或是依赖其它数据都原因而被推迟发起请求,从用户角色看,这也是一个很慢的接口。所以作为开发者还需要关注API请求是否能够尽快地被发起。
四、前端性能数据采集
通过上面的内容,我们了解了网站性能监控的一些指标,接下来看看这些指标数据是如何获取的。
1. web-vitals库
对于LCP、FID、CLS数据,可以直接安装web-vitals库:
🔗(https://github.com/GoogleChrome/web-vitals)
如何安装:
npm install web-vitals
使用方法:
import {getLCP,getFID,getCLS} from'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
打开页面,在浏览器控制台上就可以看到类似的数据:
实际使用时把console.log替换成你要处理的方法即可。当然,还可以使用getFCP、getTTFB方法来获取相应的数据。
2. performance API
为了帮助开发者更好地衡量和改进前端页面性能,W3C性能小组引入了Navigation Timing API,实现了自动、精准的页面性能打点。performance能提供哪些时间节点?在浏览器控制台中执行window.performance.timing;可以得到类似如下输出:
这些属性和值代表什么呢?之此之前,我们先来看下这张图:
上图是实时监控性能模型,可以看到我们的页面加载被定义成了很多个阶段。可以大致分为5个阶段:
1)开始计时
2)重定向
3)网络连接
4)数据交互
5)页面渲染
各属性对应的意义如下:
属性 | 说明 |
---|---|
navigationStart | 同一个浏览器上下文的上一个文档卸载结束时的时间戳,如果没有上一个文档,这个值会和fetchStart相同。 |
unloadEventStart | unload事件抛出时的时间戳,如果没有上一个文档,这个值会是0。 |
unloadEventEnd | unload事件处理完成的时间戳,如果没有上一个文档,这个值会是0。 |
redirectStart | 第一个HTTP重定向开始时的时间戳,没有重定向或者重定向中的不同源,这个值会是0。 |
redirectEnd | 最后一个HTTP重定向开始时的时间戳,没有重定向或者重定向中的不同源,这个值会是0。 |
fetchStart | 浏览器准备好使用HTTP请求来获取文档的时间戳。发送在检查缓存之前。 |
domainLookupStart | 域名查询开始的时间戳,如果使用了持续连接或者缓存,则与fetchStart一致。 |
domainLookupEnd | 域名查询结束的时间戳,如果使用了持续连接或者缓存,则与fetchStart一致。 |
connectStart | HTTP请求开始向服务器发送时的时间戳,如果使用了持续连接,则与fetchStart一致。 |
connectEnd | 浏览器与服务器之间连接建立(所有握手和认证过程全部结束)的时间戳,如果使用了持续连接,则与fetchStart一致。 |
secureConnectionStart | 浏览器与服务器开始安全链接握手时的时间戳,如果当前网页不需要安全连接,这个值会是0。 |
requestStart | 浏览器向服务器发出HTTP请求的时间戳。 |
responseStart | 浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳。 |
responseEnd | 浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳。 |
domLoading | 当前网页DOM结构开始解析时的时间戳。 |
domInteractive | 当前网页DOM结构解析完成,开始加载内嵌资源时的时间戳。 |
domContentLoadedEventStart | 需要被执行的脚本已经被解析的时间戳。 |
domContentLoadedEventEnd | 需要立即执行的脚本已经被执行的时间戳。 |
domComplete | 当前文档解析完成的时间戳。 |
loadEventStartload | 事件被发送时的时间戳,如果这个事件还未被发送,它的值将会是0。 |
loadEventEnd | load事件结束时的时间戳,如果这个事件还未被发送,它的值将会是0。 |
通过以上时间点,我们就可以计算出如下的前端性能指标,如:
重定向耗时 | redirectEnd - redirectStart |
DNS 解析耗时 | domainLookupEnd - domainLookupStart |
TCP 连接耗时 | connectEnd - connectStart |
SSL 安全连接耗时 | connectEnd - secureConnectionStart |
网络请求耗时 (TTFB) | responseStart - requestStart |
数据传输耗时 | responseEnd - responseStart |
DOM 解析耗时 | domInteractive - responseEnd |
资源加载耗时 | loadEventStart - domContentLoadedEventEnd |
首包时间 | responseStart - domainLookupStart |
白屏时间 | responseEnd - fetchStart |
首次可交互时间 | domInteractive - fetchStart |
DOM Ready 时间 | domContentLoadEventEnd - fetchStart |
首屏加载时间 | domComplete - navigationStart |
除performance.timming外,performance API 还有一些方法帮助你获取到更多有用的信息,例如:
performance.getEntries()
获取到每个资源、xmlhttpRequest的相关参数,例如发起时间,资源地址,耗时等。
点击放大
performance.getEntriesByType(type)
根据资源类型来查看某种类型的资源耗时等信息。
performance.getEntriesByName(name, type)
根据名称和资源来源来获取相关信息。
Performance.now()
可用于记录当前代码执行的时间点,不同于new Date().getTime()。
Date().getTime() | 毫秒,当前时间戳,受系统时间的影响,距离 1970 的时间;如:1619014021294 |
performance.new() | 微秒(百万分之一秒),时间是以恒定速率递增,不受系统程序执行阻塞的影响,相对于 performance.timing.navigationStart(页面初始化) 的时间。如:3956.404999946244 |
Performance.mark()
通过在各个地方打点,标记每个点的执行时间点。
3. js错误、vue错误、api错误的采集
通过window.onerror可以捕获JS错误信息:
/**
* JS 错误捕获
* @param {String} msg 错误消息
* @param {String} url 引发错误的脚本的URL
* @param {Number} lineNo 发生错误的行号
* @param {Number} columnNo 发生错误的行的列号
* @param {Object} error 错误对象
*/
window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(error.stack);
// do something.....
} ;
vue错误不能使用window.onerror来捕获,vue提供了Vue.config.errorHandler方法来捕获vue错误,例如:
/**
* Vue错误捕获
* @param {Object} error 错误对象
*/
Vue.config.errorHandler = (error) => {
console.log(error);
// do something.....
};
如果你使用axios来处理API,在axios发送请求后,使用catch,或在Axios拦截器统一处理,例如:
new Promise((resolve, reject) => {
axios.post(url, data).then((response) => {
}).catch(function(error){
console.log(error);
// do something.....
})
})
// 或在Axios拦截器统一处理
Axios.interceptors.response.use(
(response) => {
},
(error) => {
console.log(error);
// do something.....
},
);
以上就是关于前端性能监控方面的一些衡量指标、数据采集方法,为下一步的性能优化提供了方向和数据支持。
作者简介
何瑞,腾讯AB实验平台前端开发工程师。
6月5日,Techo TVP 开发者峰会 ServerlessDays China 2021,即将重磅来袭!
扫码立即参会赢好礼👇